package com.tbit.uqbike.service.business.tag.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.tbit.uqbike.enums.TagOperationType;
import com.tbit.uqbike.object.pojo.Machine;
import com.tbit.uqbike.object.pojo.tag.MachineTag;
import com.tbit.uqbike.object.pojo.tag.Tag;
import com.tbit.uqbike.object.pojo.vo.BatchOperationMsgVO;
import com.tbit.common.entity.view.PageResult;
import com.tbit.uqbike.service.base.MachineService;
import com.tbit.uqbike.service.business.tag.TagBindService;
import com.tbit.uqbike.service.business.tag.UpdateBindStatusReq;
import com.tbit.uqbike.service.business.tag.strategy.TagOperationStrategyFactory;
import com.tbit.uqbike.webmanager.business.machine_op_log.service.MachineOpLogService;
import com.tbit.uqbike.webmanager.dao.core.MachineDao;
import com.tbit.uqbike.webmanager.dao.core.tag.MachineTagDao;
import com.tbit.uqbike.webmanager.dao.core.tag.TagDao;
import com.tbit.uqbike.webmanager.dto.TagBindDTO;
import com.tbit.uqbike.webmanager.req.tag.bind.QueryBindListReq;
import com.tbit.uqbike.webmanager.resp.tag.TagBindListResp;
import com.tbit.uqbike.webmanager.resp.tag.TagResult;
import com.tbit.uqbike.webmanager.util.DbOperationUtil;
import com.tbit.uqbike.webmanager.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 车辆标签绑定
 *
 *
 * @author <a href="mailto:chen.chaoxiong@tbit.com.cn">ChenChaoXiong</a>
 * @since 2024/7/8
 */
@Service
@Validated
public class TagBindServiceImpl implements TagBindService {

    @Autowired
    private MachineService machineService;

    @Resource
    private MachineTagDao machineTagDao;
    @Resource
    private MachineDao machineDao;


    @Resource
    private TagOperationStrategyFactory tagOperationStrategyFactory;
    @Autowired
    private MachineOpLogService machineOpLogService;
    @Autowired
    private TagDao tagDao;

    /**
     * 查询车辆标签绑定列表
     *
     * @param req
     * @return
     */
    @Override
    public PageResult<List<TagBindListResp>> queryBindListPage(QueryBindListReq req) {
        Integer page = req.getPage();
        Integer size = req.getRowCount();
        PageInfo<List<TagBindListResp>> pageInfo = PageHelper.startPage(page, size)
                                                             .doSelectPageInfo(() -> machineTagDao.selectList(req));
        return new PageResult<>(page, size, pageInfo.getTotal(), pageInfo.getPages(), pageInfo.getList());
    }

    @Override
    public BatchOperationMsgVO<TagResult> batchTag(TagBindDTO tagBind) {
        String machineInfoType = tagBind.getMachineInfoType();
        TagOperationType operationType = TagOperationType.getTagOperationTypeByOperationInfo(machineInfoType);
        if (Objects.isNull(operationType)) {
            return BatchOperationMsgVO.empty();
        }
        return tagOperationStrategyFactory.getStrategy(operationType)
                                          .operate(tagBind);
    }

    // 插入车辆操作日志
    private void addOpLog(String opType, String detail, List<Machine> machines) {
        machineOpLogService.batchAddRecord("车辆标签", opType, true, detail, machines);
    }

    /**
     * 根据区域ID绑定标签
     * 查询区域下所有设备，绑定标签
     *
     * @param tagBind
     * @return
     */
    @Override
    public BatchOperationMsgVO<TagResult> tagByAccountId(TagBindDTO tagBind) {
        TagOperationType operationType = TagOperationType.ADD_BY_ACCOUNT_ID;
        BatchOperationMsgVO<TagResult> results =  tagOperationStrategyFactory.getStrategy(operationType)
                .operate(tagBind);

        // 插入车辆操作日志
        List<Tag> tags = tagDao.selectByIds(tagBind.getTagIds());
        String detail = "标签内容: " + tags.stream().map(Tag::getName).collect(Collectors.joining(","));
        List<Machine> machines = machineService.getByIds(results.getSuccessList().stream()
                                          .map(TagResult::getMachineId)
                                          .collect(Collectors.toSet()));
        addOpLog("添加", detail, machines);
        return results;
    }

    /**
     * 根据设备ID绑定标签
     *
     * @param tagBind
     * @return
     */
    @Override
    public BatchOperationMsgVO<TagResult> tagByMachineId(TagBindDTO tagBind) {
        TagOperationType operationType = TagOperationType.ADD_BY_MACHINE_ID;
        BatchOperationMsgVO<TagResult> results = tagOperationStrategyFactory.getStrategy(operationType)
                                          .operate(tagBind);

        // 插入车辆操作日志
        if (CollectionUtil.isNotEmpty(results.getSuccessList())) {
            List<Tag> tags = tagDao.selectByIds(tagBind.getTagIds());
            String detail = "标签内容: " + tags.stream().map(Tag::getName).collect(Collectors.joining(","));
            List<Machine> machines = machineService.getByIds(results.getSuccessList().stream()
                    .map(TagResult::getMachineId)
                    .collect(Collectors.toSet()));
            addOpLog("添加", detail, machines);
        }
        return results;
    }

    /**
     * 根据设备编号绑定标签
     * 通过将设备编号转换为设备ID，再绑定标签
     *
     * @param tagBind
     * @return
     */
    @Override
    public BatchOperationMsgVO<TagResult> tagByMachineNo(TagBindDTO tagBind) {
        TagOperationType operationType = TagOperationType.ADD_BY_MACHINE_NO;
        BatchOperationMsgVO<TagResult> results = tagOperationStrategyFactory.getStrategy(operationType)
                                          .operate(tagBind);

        // 插入车辆操作日志
        if (CollectionUtil.isNotEmpty(results.getSuccessList())) {
            List<Tag> tags = tagDao.selectByIds(tagBind.getTagIds());
            String detail = "标签内容: " + tags.stream().map(Tag::getName).collect(Collectors.joining(","));
            List<Machine> machines = machineService.getByIds(results.getSuccessList().stream()
                    .map(TagResult::getMachineId)
                    .collect(Collectors.toSet()));
            addOpLog("添加", detail, machines);
        }
        return results;
    }

    /**
     * 根据用户编码绑定标签
     * 通过将用户编码转换为设备ID，再绑定标签
     *
     * @param tagBind
     * @return
     */
    @Override
    public BatchOperationMsgVO<TagResult> tagByUserCode(TagBindDTO tagBind) {
        TagOperationType operationType = TagOperationType.ADD_BY_USER_CODE;
        BatchOperationMsgVO<TagResult> results = tagOperationStrategyFactory.getStrategy(operationType)
                                          .operate(tagBind);

        // 插入车辆操作日志
        if (CollectionUtil.isNotEmpty(results.getSuccessList())) {
            List<Tag> tags = tagDao.selectByIds(tagBind.getTagIds());
            String detail = "标签内容: " + tags.stream().map(Tag::getName).collect(Collectors.joining(","));
            List<Machine> machines = machineService.getByIds(results.getSuccessList().stream()
                    .map(TagResult::getMachineId)
                    .collect(Collectors.toSet()));
            addOpLog("添加", detail, machines);
        }
        return results;
    }

    /**
     * 通过tagId取消标签绑定,用于标签删除场景
     *
     * @param tagBindDTO
     * @return
     */

    @Override
    public BatchOperationMsgVO<TagResult> cancelByTagId(TagBindDTO tagBindDTO) {
        List<UpdateBindStatusReq> reqs = tagBindDTO.buildCancelReqFromTagId();
        if (CollectionUtil.isEmpty(reqs)) {
            return BatchOperationMsgVO.empty();
        }
        BatchOperationMsgVO<TagResult> results = new BatchOperationMsgVO<>();

        // 删除的车辆标签
        List<MachineTag> delTags = machineTagDao.selectTagedListByMachineIdsAndTagIds(Collections.emptySet(), reqs.stream()
                .map(UpdateBindStatusReq::getTagId)
                .collect(Collectors.toSet())
        );

        // 按标签id分组车辆标签
        Map<Long, List<MachineTag>> delTagGroupMap = delTags.stream()
                .collect(Collectors.groupingBy(MachineTag::getTagId));

        // 涉及的车辆表
        Map<Integer, Machine> machineMap = delTags.isEmpty() ? Collections.emptyMap() : machineService.getByIds(delTags.stream()
                .map(MachineTag::getMachineId)
                .collect(Collectors.toSet())
        ).stream()
                .collect(Collectors.toMap(Machine::getMachineId, Function.identity()));

        // 涉及的标签表
        Map<Long, Tag> tagMap = delTags.isEmpty() ? Collections.emptyMap() : tagDao.selectByIds(delTags.stream()
                .map(MachineTag::getTagId)
                .collect(Collectors.toSet())
        ).stream()
                .collect(Collectors.toMap(Tag::getId, Function.identity()));

        reqs.forEach(req -> {
            String tagIdStr = req.getTagId().toString();
            TagResult.TagResultBuilder builder = TagResult.builder()
                                                          .brandId(req.getBrandId())
                                                          .accountId(req.getAccountId())
                                                          .tagId(tagIdStr);
            if (DbOperationUtil.isSuccess(machineTagDao.updateStatusByTagId(req))) {
                results.putSuccessValue(builder.success(true).build());
            } else {
                results.putFailValue(builder.success(false).message("Cancel failed").build());
            }
        });

        // 插入车辆操作日志
        if (CollUtil.isNotEmpty(results.getSuccessList())) {
            // 按标签维度记录
            for (TagResult tagResult : results.getSuccessList()) {
                Long tagId = Long.valueOf(tagResult.getTagId());
                Tag tag = tagMap.get(tagId);

                List<Machine> delTagMachines = delTagGroupMap.get(tagId).stream()
                        .map(mt -> machineMap.get(mt.getMachineId()))
                        .collect(Collectors.toList());

                // 插入车辆操作日志
                String detail = "标签内容: " + tag.getName();
                addOpLog("删除", detail, delTagMachines);
            }
        }


        return results;
    }


    /*
     * 通过设备ID取消标签绑定
     * 通过将TagBindDTO的tagIds置为空，可用于设备删除场景
     */
    @Override
    public BatchOperationMsgVO<TagResult> cancelByMachineId(TagBindDTO tagBindDTO) {
        List<UpdateBindStatusReq> reqs = tagBindDTO.buildCancelReqFromMachineId();
        if (CollectionUtil.isEmpty(reqs)) {
            return BatchOperationMsgVO.empty();
        }
        BatchOperationMsgVO<TagResult> results = new BatchOperationMsgVO<>();
        reqs.forEach(req -> {
            TagResult.TagResultBuilder builder = TagResult.builder()
                                                          .brandId(req.getBrandId())
                                                          .accountId(req.getAccountId())
                                                          .machineId(req.getMachineId());
            if (DbOperationUtil.isSuccess(machineTagDao.updateStatusByMachineId(req))) {
                results.putSuccessValue(builder.success(true).build());
            } else {
                results.putFailValue(builder.success(false).message("Cancel failed").build());

            }
        });

        // 插入车辆操作日志
        if (CollUtil.isNotEmpty(results.getSuccessList()) && CollUtil.isNotEmpty(tagBindDTO.getTagIds())) {
            List<Machine> delTagMachines = machineService.getByIds(results.getSuccessList().stream()
                    .map(TagResult::getMachineId)
                    .collect(Collectors.toSet())
            );
            List<Tag> tags = tagDao.selectByIds(tagBindDTO.getTagIds());
            addOpLog("删除", "标签内容: " + tags.stream()
                    .map(Tag::getName)
                    .collect(Collectors.joining(",")), delTagMachines);
        }

        return results;

    }

    @Override
    public void deleteByMachineId(List<Integer> machineIds) {
       if (CollectionUtil.isNotEmpty(machineIds)) {
           machineTagDao.deleteByMachineIds(machineIds);
       }
    }

    /**
     * 通过设备编号取消标签绑定
     *
     * @param tagBindDTO
     * @return
     */
    @Override
    public BatchOperationMsgVO<TagResult> cancelByMachineNo(TagBindDTO tagBindDTO) {
        Set<String> machineNos = tagBindDTO.getMachineNos();
        BatchOperationMsgVO<TagResult> results = new BatchOperationMsgVO<>();
        Map<String, Object> params = new HashMap<>();
        params.put("accountIds", Collections.singletonList(tagBindDTO.getAccountId()));
        params.put("machineNOs", StringUtil.getArrayListString(new ArrayList<>(machineNos)));
        List<Machine> machines = Optional.ofNullable(machineDao.getByAccountIdAndMachineNOs(params))
                                            .orElse(Collections.emptyList());

        Map<Integer,String> existMachineMap = machines.stream()
                                                        .collect(Collectors.toMap(Machine::getMachineId, Machine::getMachineNO));

        Set<String> notExistMachineNos = machineNos.stream()
                                                    .filter(machineNo ->
                                                            !existMachineMap.containsValue(machineNo)
                                                    )
                                                    .collect(Collectors.toSet());

        if (CollectionUtil.isNotEmpty(notExistMachineNos)) {
            results.putAllFailValue(
                    notExistMachineNos.stream()
                                      .map( machineNo ->
                                              TagResult.builder()
                                                       .brandId(tagBindDTO.getBrandId())
                                                       .accountId(tagBindDTO.getAccountId())
                                                       .machineNo(machineNo)
                                                       .message("Machine does not exist")
                                                       .build()
                                              ).collect(Collectors.toList())
            );
        }
        if (CollectionUtil.isEmpty(existMachineMap)) {
            return results;
        }

        Set<Integer> machineIds = machines.stream()
                                           .map(Machine::getMachineId)
                                           .collect(Collectors.toSet());
        tagBindDTO.setMachineIds(machineIds);

        List<UpdateBindStatusReq> reqs = tagBindDTO.buildCancelReqFromMachineId();
        reqs.forEach(req -> {
            TagResult.TagResultBuilder builder = TagResult.builder()
                                                          .brandId(req.getBrandId())
                                                          .accountId(req.getAccountId())
                                                          .machineId(req.getMachineId())
                                                          .machineNo(existMachineMap.get(req.getMachineId()));

            if (DbOperationUtil.isSuccess(machineTagDao.updateStatusByMachineId(req))) {
                results.putSuccessValue(builder.success(true).build());
            } else {
                results.putFailValue(builder.success(false).message("Cancel failed").build());
            }
        });

        // 插入车辆操作日志
        if (CollUtil.isNotEmpty(results.getSuccessList()) && CollUtil.isNotEmpty(tagBindDTO.getTagIds())) {
            List<Machine> delTagMachines = machineService.getByIds(results.getSuccessList().stream()
                    .map(TagResult::getMachineId)
                    .collect(Collectors.toSet())
            );
            List<Tag> tags = tagDao.selectByIds(tagBindDTO.getTagIds());
            addOpLog("删除", "标签内容: " + tags.stream()
                    .map(Tag::getName)
                    .collect(Collectors.joining(",")), delTagMachines);
        }

        return results;
    }

    /**
     * 通过用户编码取消标签绑定
     *
     * @param tagBindDTO
     * @return
     */
    @Override
    public BatchOperationMsgVO<TagResult> cancelByUserCode(TagBindDTO tagBindDTO) {
        Set<String> userCodes = tagBindDTO.getUserCodes();
        BatchOperationMsgVO<TagResult> results = new BatchOperationMsgVO<>();
        Map<String, Object> param = new HashMap<>();
        param.put("accountId", tagBindDTO.getAccountId());
        param.put("userCodes", StringUtil.getArrayListString(new ArrayList<>(userCodes)));
        List<Machine> machines = Optional.ofNullable(machineDao.getByAccountIdAndUserCodes(param))
                                         .orElse(Collections.emptyList());

        Map<Integer,String> existMachineMap = machines.stream()
                                                        .collect(Collectors.toMap(Machine::getMachineId, Machine::getUserCode));

        Set<String> notExistUserCodes = userCodes.stream()
                                                  .filter(userCode ->
                                                          !existMachineMap.containsValue(userCode)
                                                  )
                                                  .collect(Collectors.toSet());

        if (CollectionUtil.isNotEmpty(notExistUserCodes)) {
            results.putAllFailValue(
                    notExistUserCodes.stream()
                                     .map(userCode ->
                                             TagResult.builder()
                                                      .brandId(tagBindDTO.getBrandId())
                                                      .accountId(tagBindDTO.getAccountId())
                                                      .userCode(userCode)
                                                      .message("Machine does not exist")
                                                      .build()
                                     ).collect(Collectors.toList())
            );
        }
        if (CollectionUtil.isEmpty(existMachineMap)) {
            return results;
        }

        Set<Integer> machineIds = machines.stream()
                                           .map(Machine::getMachineId)
                                           .collect(Collectors.toSet());
        tagBindDTO.setMachineIds(machineIds);

        List<UpdateBindStatusReq> reqs = tagBindDTO.buildCancelReqFromMachineId();
        reqs.forEach(req -> {
            TagResult.TagResultBuilder builder = TagResult.builder()
                                                          .brandId(req.getBrandId())
                                                          .accountId(req.getAccountId())
                                                          .machineId(req.getMachineId())
                                                          .userCode(existMachineMap.get(req.getMachineId()));

            if (DbOperationUtil.isSuccess(machineTagDao.updateStatusByMachineId(req))) {
                results.putSuccessValue(builder.success(true).build());
            } else {
                results.putFailValue(builder.success(false).message("Cancel by usercode failed").build());
            }
        });


        // 插入车辆操作日志
        if (CollUtil.isNotEmpty(results.getSuccessList()) && CollUtil.isNotEmpty(tagBindDTO.getTagIds())) {
            List<Machine> delTagMachines = machineService.getByIds(results.getSuccessList().stream()
                    .map(TagResult::getMachineId)
                    .collect(Collectors.toSet())
            );
            List<Tag> tags = tagDao.selectByIds(tagBindDTO.getTagIds());
            addOpLog("删除", "标签内容: " + tags.stream()
                    .map(Tag::getName)
                    .collect(Collectors.joining(",")), delTagMachines);
        }

        return results;

    }

    /**
     * 通过id取消标签绑定
     *
     * @param tagBindDTO
     * @return
     */
    @Override
    public BatchOperationMsgVO<TagResult> cancelByid(TagBindDTO tagBindDTO) {
        List<UpdateBindStatusReq> reqs = tagBindDTO.buildCancelReqFromId();
        if (CollectionUtil.isEmpty(reqs)) {
            return BatchOperationMsgVO.empty();
        }
        BatchOperationMsgVO<TagResult> results = new BatchOperationMsgVO<>();

        Map<String, MachineTag> delTags = machineTagDao.selectByIds(reqs.stream()
                .map(UpdateBindStatusReq::getId)
                .collect(Collectors.toList())
        ).stream().collect(Collectors.toMap(mt -> mt.getId().toString(), Function.identity()));
        Map<Long, Tag> tags = tagDao.selectByIds(delTags.values().stream()
                .map(MachineTag::getTagId)
                .collect(Collectors.toSet())
        ).stream().collect(Collectors.toMap(Tag::getId, Function.identity()));

        reqs.forEach(req -> {
            TagResult.TagResultBuilder builder = TagResult.builder()
                    .brandId(req.getBrandId())
                    .accountId(req.getAccountId())
                    .machineId(req.getMachineId())
                    .id(req.getId().toString());
            if (DbOperationUtil.isSuccess(machineTagDao.updateStatusById(req))) {
                results.putSuccessValue(builder.success(true).build());
            } else {
                results.putFailValue(builder.success(false).message("Cancel by id failed").build());
            }
        });
        fillTagBindListResp(results);

        // 插入车辆操作日志
        Map<Integer, Machine> delTagMachines = machineService.getByIds(results.getSuccessList().stream()
                .map(TagResult::getMachineId)
                .collect(Collectors.toSet())
        ).stream().collect(Collectors.toMap(Machine::getMachineId, Function.identity()));
        for (TagResult result : results.getSuccessList()) {
            MachineTag machineTag = delTags.get(result.getId());
            Tag tag = tags.get(machineTag.getTagId());
            Machine machine = delTagMachines.get(machineTag.getMachineId());
            if (tag != null && machine != null) {
                String detail = "标签内容: " + tag.getName();
                addOpLog("删除", detail, Collections.singletonList(machine));
            }
        }

        return results;
    }

    private void fillTagBindListResp(BatchOperationMsgVO<TagResult> results) {
        List<Integer> allIds = Stream.of(results.getSuccessList(), results.getFailList(), results.getAbnormalList())
                                     .flatMap(Collection::stream)
                                     .map(TagResult::getId)
                                     .map(Integer::parseInt)
                                     .collect(Collectors.toList());

        if (CollectionUtil.isEmpty(allIds)) {
            return;
        }

        List<TagBindListResp> tagBindListResps = machineTagDao.selectAll(allIds);
        if (CollectionUtil.isEmpty(tagBindListResps)) {
            return;
        }

        Map<Integer, TagBindListResp> tagBindListRespMap = tagBindListResps.stream()
                                                                           .collect(Collectors.toMap(resp -> Integer.parseInt(resp.getId()), resp -> resp));

        processResults(results.getSuccessList(), tagBindListRespMap);
        processResults(results.getFailList(), tagBindListRespMap);
        processResults(results.getAbnormalList(), tagBindListRespMap);
    }

    private void processResults(List<TagResult> resultList, Map<Integer, TagBindListResp> tagBindListRespMap) {
        resultList.forEach(tagResult -> {
            Integer id = Integer.parseInt(tagResult.getId());
            TagBindListResp tagBindListResp = tagBindListRespMap.get(id);
            if (tagBindListResp != null) {
                tagResult.setBrandId(Integer.parseInt(tagBindListResp.getBrandId()));
                tagResult.setAccountId(Integer.parseInt(tagBindListResp.getAccountId()));
                tagResult.setMachineId(tagBindListResp.getMachineId());
                tagResult.setMachineNo(tagBindListResp.getMachineNo());
                tagResult.setUserCode(tagBindListResp.getUserCode());
            }
        });
    }

    private List<Machine> getMachinesByTagIds(TagBindDTO tagBindDTO) {
        Set<Long> tagIds = tagBindDTO.getTagIds();
        if (CollectionUtil.isEmpty(tagIds)) {
            return Collections.emptyList();
        }
        Set<Integer> machineIds = machineTagDao.selectUniqueMachineIdsByTagIds(tagIds);
        if (CollectionUtil.isEmpty(machineIds)) {
            return Collections.emptyList();
        }
        return Optional.ofNullable(machineService.getByIds(machineIds))
                       .orElse(Collections.emptyList());
    }


    private List<Machine> getMachinesByMachineIds(TagBindDTO tagBindDTO) {
        Set<Integer> machineIds = tagBindDTO.getMachineIds();
        if (CollectionUtil.isEmpty(machineIds)) {
            return Collections.emptyList();
        }
        return Optional.ofNullable(machineService.getByIds(machineIds))
                       .orElse(Collections.emptyList());
    }

    private List<Machine> getMachinesByMachineNos(TagBindDTO tagBindDTO) {
        Set<String> machineNos = tagBindDTO.getMachineNos();
        if (CollectionUtil.isEmpty(machineNos)) {
            return Collections.emptyList();
        }
        return Optional.ofNullable(machineService.getByMachineNOs(new ArrayList<>(machineNos)))
                       .orElse(Collections.emptyList());
    }

    private List<Machine> getMachinesByBindIds(TagBindDTO tagBindDTO) {
        Set<Long> ids = tagBindDTO.getIds();
        if (CollectionUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        Set<Integer> machineIds = machineTagDao.selectUniqueMachineIdsByIds(ids);
        if (CollectionUtil.isEmpty(machineIds)) {
            return Collections.emptyList();
        }
        return Optional.ofNullable(machineService.getByIds(machineIds))
                       .orElse(Collections.emptyList());
    }



}